package org.http4k.filter

import org.http4k.core.Filter
import org.http4k.core.HttpHandler
import org.http4k.core.HttpMessage
import org.http4k.core.MemoryBody
import org.http4k.core.NoOp
import org.http4k.core.Response
import org.http4k.core.then
import org.http4k.filter.DebuggingFilters.PrintRequestAndResponse
import org.http4k.routing.RoutingHttpHandler
import java.io.PrintStream
import java.lang.management.ManagementFactory.getRuntimeMXBean

object DebuggingFilters {
    private const val defaultDebugStream = false

    /**
     * Print details of the request before it is sent to the next service.
     */
    object PrintRequest {
        operator fun invoke(out: PrintStream = System.out, debugStream: Boolean = defaultDebugStream): Filter =
            RequestFilters.Tap { req ->
                out.println(
                    listOf(
                        "***** REQUEST: ${req.method}: ${req.uri} *****",
                        req.printable(debugStream)
                    ).joinToString("\n")
                )
            }
    }

    /**
     * Print details of the response before it is returned.
     */
    object PrintResponse {
        operator fun invoke(out: PrintStream = System.out, debugStream: Boolean = defaultDebugStream, shouldReport: (Response) -> Boolean = { true }): Filter =
            Filter { next ->
                {
                    try {
                        next(it).let { response ->
                            out.println(
                                listOfNotNull(
                                    "***** RESPONSE ${response.status.code} to ${it.method}: ${it.uri} *****",
                                    response.printable(debugStream).takeIf { shouldReport(response) }
                                ).joinToString("\n")
                            )
                            response
                        }
                    } catch (e: Exception) {
                        out.println("***** RESPONSE FAILED to ${it.method}: ${it.uri} *****")
                        e.printStackTrace(out)
                        throw e
                    }
                }
            }
    }

    private fun HttpMessage.printable(debugStream: Boolean) =
        if (debugStream || body is MemoryBody) this else body("<<stream>>")

    /**
     * Print details of a request and it's response.
     */
    object PrintRequestAndResponse {
        operator fun invoke(out: PrintStream = System.out, debugStream: Boolean = defaultDebugStream) =
            PrintRequest(out, debugStream).then(PrintResponse(out, debugStream))
    }
}

fun HttpHandler.debug(out: PrintStream = System.out, debugStream: Boolean = false) =
    PrintRequestAndResponse(out, debugStream).then(this)

fun RoutingHttpHandler.debug(out: PrintStream = System.out, debugStream: Boolean = false) =
    PrintRequestAndResponse(out, debugStream).then(this)

fun Filter.inIntelliJOnly() = when {
    getRuntimeMXBean().inputArguments.find { it.contains("idea", true) } != null -> this
    else -> Filter.NoOp
}

